import { Table } from "element-ui";
import "./cTable.less";
import slotsVnode from "@/components/mixins/slotsVnode";
import Sortable from "sortablejs"; // 拖拽排序插件
const pageSizes = [10, 20, 30, 50];
const btnSize = "small";

/*
 * cTable
 * 自执行父组件的getList方法(若存在getList),并且需要将接口的返回值return {rows:[],total:100}
 * loading 若外部getList为Promise则自动添加loading
 * cTable 自适应外部盒子高度,如果超出容器,则固定表头滚动
 * 支持插槽 topLeft、topRight、bottomLeft、bottomRight
 * showHeader --> 控制 头部,其中 showSearch --> 是否显示搜索
 * showBottom --> 控制 底部,其中 showPagination --> 是否显示分页
 * deleteRows || selectionChange 其中存在一个就会显示table的复选框
 * row-key:行数据的 Key，用来优化 Table 的渲染；在使用 reserve-selection 功能与显示树形数据时，该属性是必填的。类型为 String
 *  */

const customProps = {
  elementLoadingText: {
    default: "加载中",
    type: String,
  },
  showPagination: {
    // 是否显示分页
    default: true,
    type: Boolean,
  },
  showNumber: {
    // 是否显示序号
    default: true,
    type: Boolean,
  },
  showBottom: {
    // 是否显示底部
    default: true,
    type: Boolean,
  },
  showHeader: {
    // 是否显示header
    default: true,
    type: Boolean,
  },
  showSearch: {
    // 是否显示搜索
    default: true,
    type: Boolean,
  },
  defaultTrigger: {
    // 是否在created自动调用getList
    default: true,
    type: Boolean,
  },
  columns: {
    // 表格列的配置描述,也就是el-table-column,若数据为空会使用 '-' 替代
    default: () => [],
    type: Array,
  },
  placeholder: String, // 搜索输入框的提示文字
  sortable: {
    // 是否在可拖动table
    default: false,
    type: Boolean,
  },
  batchOption: {
    // 选择全部(btn1)、反向选择(btn2)、批量删除(btn3) 的配置
    /*
     *  写法
     * batchOption:{btn1:{text:'批量删除',hasPermi:['sys:user:add']}}
     * text:按钮文字
     * hasPermi:按钮的权限array
     *  */
    default: () => ({}),
    type: Object,
  },
  selectOptions: {
    // type="selection" 复选框的props
    default: () => ({}),
    type: Object,
  },
  // 取消自适应父容器高度
  cancelAdapt: Boolean,
  // 是否显示多选框
  showSelection: Boolean,
  loading: Boolean,
};
const cTable = {
  name: "cTable",
  props: {
    ...Table.props,
    border: {
      default: true,
      type: Boolean,
    },
    stripe: {
      // 是否为斑马纹 table
      default: true,
      type: Boolean,
    },
    /* 以下为自定义props */
    ...customProps,
  },
  mixins: [slotsVnode],
  inheritAttrs: false,
  data() {
    return {
      searchVal: "", //搜索value
      selectRows: [], // 选中的行
      total: 0,
      pageSize: 10, // 默认每页显示10条
      pageNum: 1, // 当前页
      tabData: [],
      tabContentHeight: null,
      tableLoading: this.loading,
    };
  },
  computed: {
    isInitFn() {
      // Boolean
      return !!this.$listeners.getList;
    },
    tableProps() {
      let tabProps = {};
      let customPropsKeys = Object.keys(customProps); // 忽略自定义Props,table组件不需要
      for (const k in this.$props) {
        // 过滤掉自定义propsP
        if (!customPropsKeys.includes(k)) {
          tabProps[k] = this.$props[k];
        }
      }
      return {
        ...tabProps,
        height: this.height || this.tabContentHeight,
        data: this.tabData,
        "row-key": tabProps.rowKey ? tabProps.rowKey : "id", // this.selectRows依赖这个属性
      };
    },
    __listener() {
      return {
        "selection-change": this.selectionChange,
        select: this.handleSelect,
        "select-all": this.selectAllChange,
        ...this.$listeners,
      };
    },
  },
  watch: {
    data: {
      handler(newData) {
        if (!newData || newData.length === 0) {
          this.tabData = [];
          return;
        }
        this.tabData = newData;
      },
      deep: true,
      immediate: true,
    },
    tabData: {
      handler(newData) {
        if (this.height || this.height === 0) {
          // 外部自定义高度
          return;
        }
        if (this.cancelAdapt) return; // 取消自适应父容器高度
        if (!newData || newData.length === 0) {
          this.tabContentHeight = undefined;
          return;
        }
        this.$nextTick(() => {
          // 自适应高度-固定表头
          const partent = this.$refs.contentTable.clientHeight;
          this.tabContentHeight = partent ? partent : undefined; // 父容器可能是display: none;让c-table height:100%
        });
      },
      immediate: true,
    },
    loading: {
      handler(value) {
        this.tableLoading = value;
      },
      immediate: true,
    },
  },
  created() {
    this.defaultTrigger && this.getList();
  },
  mounted() {
    this.sortable && this.rowDrop(); // 拖动表格,触发dragEnd,并返回拖动后的数据
  },
  methods: {
    /** 清空表格数据 (外部ref调用) */
    resetTableData() {
      this.tabData = [];
      this.pageNum = 1;
      this.total = 0;
    },
    /** 清空搜索框的值 (外部ref调用) */
    resetSearchVal() {
      this.searchVal = "";
    },
    /**
     * 设置表格选中/取消选中
     * @param {Object} item 选中项
     * @param {Boolean} selected 是否选中
     * */
    toggleRowSelection(item, selected) {
      // 兼容 toggleRowSelection 的选中/取消选中（因为toggleRowSelection返回的是this.selectRows）
      // selectRows存在的目的是处理分页后选中
      const rowKey = this.tableProps["row-key"];
      const idx = this.selectRows.findIndex(
        (it) => item[rowKey] === it[rowKey]
      );
      if (idx === -1 && selected) {
        // 选中
        // 若不存在，才添加
        this.selectRows.push(item);
      }
      if ((idx || idx === 0) && !selected) {
        // 取消选中
        // 若存在，才删除
        this.selectRows.splice(idx, 1);
      }
      this.$refs.elTable.toggleRowSelection(item, selected);
    },
    /**
     * 全选
     * @param {Array} selection
     * */
    selectAllChange(selection) {
      const isSelect = !!selection.length;
      let newData = selection;
      if (!isSelect) {
        newData = this.tabData;
      }
      this.selectTabRows(newData, isSelect);
    },
    /**
     * 获取选中项
     *  */
    getTableSelect() {
      return this.selectRows;
    },
    /**
     * 清空表格的选中,禁用项不取消选中
     *  */
    clearTableSelect() {
      if (this.selectRows.length === 0) return;
      this.tabData.forEach((item, index) => {
        const selectable = this.$refs.elTable.store.states.selectable; // 外部没传该函数为undefined
        if (selectable && !selectable(item, index)) {
          // 判断是否禁用 true可勾选，false禁用
          return; // 跳出本次循环
        }
        this.$refs.elTable.toggleRowSelection(item, false);
      });
      this.selectRows = [];
    },
    /**
     * @param {Array} selection
     * @param {Boolean} isSelect
     * */
    selectTabRows(selection, isSelect) {
      const rowKey = this.tableProps["row-key"];
      if (isSelect) {
        // 选中
        selection.forEach((item) => {
          const idx = this.selectRows.findIndex(
            (it) => item[rowKey] === it[rowKey]
          );
          if (idx !== -1) {
            // 删除已存在的项
            this.selectRows.splice(idx, 1);
          }
        });
        this.selectRows.push(...selection);
        return;
      }
      // 取消选中
      const ids = selection.map((item) => item[rowKey]);
      this.selectRows = this.selectRows.filter(
        (item) => !ids.includes(item[rowKey])
      );
    },
    handleSelect(selection, row) {
      // 单选
      const rowKey = this.tableProps["row-key"];
      const isSelect = selection.some((item) => item[rowKey] === row[rowKey]);
      this.selectTabRows([row], isSelect);
    },
    //行拖拽
    rowDrop() {
      // 阻止默认行为
      document.body.ondrop = function (event) {
        event.preventDefault();
        event.stopPropagation();
      };
      const cTable = this.$refs.elTable.$el;
      const tbody = cTable.querySelector(".el-table__body-wrapper tbody");
      const _this = this;
      const options = {
        onEnd: ({ newIndex, oldIndex }) => {
          if (newIndex === oldIndex) return;
          const newData = [..._this.tabData];
          // 将oldIndex项删除,移动到newIndex
          newData.splice(newIndex, 0, newData.splice(oldIndex, 1)[0]);
          _this.tabData = [];
          this.$nextTick(() => {
            _this.tabData = newData;
            this.$emit(
              "dragEnd",
              newData.map((item, i) => ({
                ...item,
                serial: this.indexMethod(i),
              }))
            );
          });
        },
      };
      Sortable.create(tbody, options);
    },
    selectAll() {
      // 选择全部
      // const arr = [...this.selectRows]; todo bug 每点一次选择全部就会重复加入选中的数据，使用过滤id方式体验跟性能代价不成正比，后续优化使用 obj存入每一页选择的数据
      const arr = [];
      this.tabData.forEach((item, index) => {
        const selectable = this.$refs.elTable.store.states.selectable; // 外部没传该函数为undefined
        if (!selectable || selectable(item, index)) {
          // 判断是否禁用 true可勾选，false禁用
          arr.push(item);
          this.$refs.elTable.toggleRowSelection(item, true);
        }
      });
      this.selectRows = arr;
    },
    reverseSelection() {
      // 反选
      const arr = []; // 只针对当前页反选
      this.tabData.forEach((item, index) => {
        const selectable = this.$refs.elTable.store.states.selectable; // 外部没传该函数为undefined
        if (selectable && !selectable(item, index)) {
          // 判断是否禁用 true可勾选，false禁用
          return; // 跳出本次循环
        }
        const flag = this.selectRows.some((it) => item.id === it.id); // flag为true代表当前是已选中
        !flag && arr.push(item);
        this.$refs.elTable.toggleRowSelection(item, !flag);
      });
      this.selectRows = arr;
    },
    isEmpty(value) {
      return value || value === 0 ? value : "-";
    },
    renderColumns(columns) {
      if (columns && columns.length) {
        return columns.map((item) => {
          const { prop, property } = item;
          const formatter = (row) => {
            // row, column, cellValue, index
            if (prop || property) {
              // 比如自定义操作栏 不需要 formatter
              return this.isEmpty(row[prop || property]); // value为空默认给 '-'
            }
          };

          let columnAttrs = {
            props: {
              showOverflowTooltip: true, // 默认当内容过长被隐藏时显示 tooltip,TODO 暂未考虑斜杠方式
              align: "center", // 默认居住
              formatter,
              ...item, // 上部分可通过item外部传进来覆盖
            },
          };
          const scopedSlots = {};
          if (item.customRender) {
            // 自定义列
            scopedSlots.default = (scope) => {
              //自定义列的内容，参数为 { row, column, $index }
              return item.customRender(scope);
            };
          }

          if (item.customHeader) {
            // 自定义头
            scopedSlots.header = (scope) => {
              return item.customHeader(scope);
            };
          }
          columnAttrs.scopedSlots = scopedSlots;
          return (
            <el-table-column {...columnAttrs}>
              {
                // 多级表头
                item.columns?.length ? this.renderColumns(item.columns) : null
              }
            </el-table-column>
          );
        });
      }

      return this.slotsVnode();
    },
    topLeftVnode() {
      if (this.$slots.topLeft) {
        return <div>{this.$slots.topLeft}</div>;
      }
      if (this.showSearch) {
        const { searchBtn = {}, resetBtn = {} } = this.batchOption;
        return (
          <div>
            <c-input
              class="search-input"
              value={this.searchVal}
              size="small"
              clearable
              placeholder={this.placeholder}
              on-input={(val) => {
                this.searchVal = val;
              }}
              on-change={(val) => {
                this.searchVal = val.trim();
                this.reload(1, 0, 10); // num重置到第一页
              }}
            ></c-input>

            <c-button
              size={btnSize}
              class="header-search-btn"
              type="primary"
              icon="el-icon-search"
              hasPermi={searchBtn.hasPermi}
              on-click={() => {
                this.reload(1);
              }}
            >
              {searchBtn.text || "查询"}
            </c-button>

            <c-button
              size={btnSize}
              style="margin-left:0"
              icon="el-icon-refresh-left"
              hasPermi={resetBtn.hasPermi}
              on-click={() => {
                this.searchVal = "";
                this.reload(1, null, 10);
              }}
            >
              {resetBtn.text || "重置"}
            </c-button>
          </div>
        );
      }
    },
    headerVnode() {
      /* showHeader 控制是否显示 */
      if (!this.showHeader) {
        return;
      }
      return (
        <div class="tb-header">
          {this.topLeftVnode()}

          {this.$slots.topRight ? <div>{this.$slots.topRight}</div> : null}
        </div>
      );
    },
    /**
     * 刷新表格
     * @param num {number} 修改页码
     * @param deleteLength {number} 删除多少条数据
     */
    reload(num, deleteLength, pageSize, ...args) {
      this.pageNum = num ? num : this.pageNum; // 设置 当前页
      if (pageSize) {
        // 每页显示的条数
        this.pageSize = pageSize;
      }
      const dataLength = (this.tabData || []).length;
      if (deleteLength === dataLength && this.pageNum > 1) {
        // 处理删除操作,当前页的数据为空还停留在当前页,需要传入deleteLength
        this.pageNum -= 1;
      }
      this.getList(args);
    },
    bottomRightVnode() {
      if (this.$slots.bottomRight) {
        return this.$slots.bottomRight;
      }

      if (this.showPagination) {
        // showPagination 是否显示分页
        return (
          <div>
            <el-pagination
              current-page={this.pageNum}
              pageSizes={pageSizes}
              pageSize={this.pageSize}
              layout="total, sizes, prev, pager, next, jumper"
              total={this.total}
              on-size-change={this.sizeChange}
              on-current-change={this.currentChange}
            ></el-pagination>
          </div>
        );
      }
    },
    bottomVnode() {
      if (!this.showBottom) {
        return;
      }
      // 选择全部(btn1)、反向选择(btn2)、批量删除(btn3) 的配置
      // 写法 batchOption:{btn1:{text:'批量删除',hasPermi:['sys:user:add']}} //hasPermi:按钮的权限array
      const { btn1 = {}, btn2 = {}, btn3 = {} } = this.batchOption;
      const selectLength = this.selectRows.length;
      return (
        <div class="c-pagination">
          {this.$slots.bottomLeft ? (
            this.$slots.bottomLeft
          ) : (
            <div class="footer-left">
              <c-button
                hasPermi={btn1.hasPermi}
                disabled={this.tabData?.length === 0}
                on-click={this.selectAll}
                size={btnSize}
              >
                {btn1.text || "选择全部"}
              </c-button>
              <c-button
                hasPermi={btn2.hasPermi}
                disabled={this.tabData?.length === 0}
                on-click={this.reverseSelection}
                size={btnSize}
              >
                {btn2.text || "反向选择"}
              </c-button>
              <c-button
                hasPermi={btn3.hasPermi}
                disabled={this.selectRows?.length === 0}
                on-click={this.deleteRows}
                size={btnSize}
              >
                {btn3.text || "批量删除"}
                {selectLength ? `(${selectLength})` : ""}
              </c-button>
              {this.$slots.remainSpace ? this.$slots.remainSpace : null}
            </div>
          )}

          {this.bottomRightVnode()}
        </div>
      );
    },
    selectionChange() {
      // 为什么需要触发一下selectionChange?
      // 因为el-table需要的是短横线的selection-change

      // 注意：selectionChange是c-table自定义的逻辑,selection-change是el-table的
      this.$nextTick(() => {
        this.$emit("selectionChange", this.selectRows);
      });
    },
    currentChange(currentNum) {
      // 当前的页数 改变时会触发
      this.pageNum = currentNum;
      if (this.isInitFn) {
        this.getList();
      }
    },
    sizeChange(size) {
      // 展示多少条数 改变时会触发
      this.pageNum = 1; // 若不设置1,当在第二页,total小于20,切换展示条数,会触发currentChange (调用两次接口,其中一次currentNum不正确)
      this.pageSize = size;
      if (this.isInitFn) {
        this.getList();
      }
    },
    indexMethod(index) {
      return (this.pageNum - 1) * this.pageSize + (index + 1);
    },
    echoSelect() {
      // 回显选中
      if (!this.tabData) return;
      const rowKey = this.tableProps["row-key"];
      const ids = this.selectRows.map((i) => i[rowKey]);
      this.tabData.forEach((item) => {
        if (ids.includes(item[rowKey])) {
          this.$nextTick(() => {
            this.$refs.elTable.toggleRowSelection(item, true);
          });
        }
      });
    },
    async dispatch(res) {
      if (!this.isInitFn) {
        // 外部不存在getList方法
        return;
      }
      const { deleteRows } = this.$listeners;
      if (res instanceof Promise) {
        // 如果为Promise
        try {
          this.tableLoading = true;
          const { rows, total } = (await res) || {};
          this.tabData = rows;
          // 显示了btn3
          deleteRows && this.echoSelect();
          this.total = total;
          this.tableLoading = false;
        } catch (e) {
          this.tableLoading = false;
          throw e;
        }
      } else {
        // res 为对象
        const { rows, total } = res || {};
        this.tabData = rows;
        this.total = total;
        // 显示了btn3
        deleteRows && this.echoSelect();
      }
    },
    /**
     * @param {Object} args
     * */
    getList(args) {
      // args可通过reload方法传入
      if (!this.isInitFn) {
        // 外部不存在getList方法
        return;
      }
      const { pageNum, pageSize, searchVal } = this.getParams();
      // 如果getList为promise则自动加上loading
      this.dispatch(
        this.$listeners.getList({ pageNum, pageSize }, searchVal, args)
      );
    },
    getParams() {
      let params = {};
      if (this.showPagination) {
        // 是否显示分页
        const { pageSize, pageNum, searchVal } = this;
        params = {
          pageNum, // 当前页
          pageSize, // 默认每页显示10条
          searchVal, // 搜索值
        };
      }
      return params;
    },
    /** 触发批量删除  */
    async deleteRows() {
      const { deleteRows } = this.$listeners;
      if (!deleteRows) return;
      const res = deleteRows(this.selectRows);
      if (res instanceof Promise) {
        await res;
      }
      this.clearTableSelect(); // 清空表格的选中,禁用项不取消选中
    },
    elTable() {
      // 返回el-table
      return this.$refs.elTable;
    },
  },
  render() {
    let { deleteRows, selectionChange } = this.$listeners;
    // 兼容驼峰和短横线命名
    selectionChange = selectionChange || this.$listeners["selection-change"];
    deleteRows = deleteRows || this.$listeners["delete-rows"];
    const { showSelection } = this;
    return (
      <div
        class="c-table"
        id="c-table-box"
        style={{ height: this.cancelAdapt ? "auto" : "100%" }}
      >
        {this.headerVnode()}

        {/* key 高度变化重新渲染 fixed */}
        <div
          style={
            this.height || this.height === 0
              ? `height:${this.height}px;flex:0 1 auto`
              : null
          }
          class="content-table"
          ref="contentTable"
        >
          <Table
            ref="elTable"
            v-loading={this.tableLoading}
            element-loading-text={this.elementLoadingText}
            class="c-table"
            style={{ position: this.cancelAdapt ? "static" : "absolute" }}
            props={this.tableProps}
            attrs={this.$attrs}
            on={this.__listener}
          >
            {deleteRows || selectionChange || showSelection ? (
              <el-table-column
                align="center"
                type="selection"
                width="55"
                props={this.selectOptions}
              ></el-table-column>
            ) : null}

            {this.showNumber ? (
              <el-table-column
                type="index"
                index={this.indexMethod}
                label="序号"
                width="70px"
                align="center"
              ></el-table-column>
            ) : null}

            {this.renderColumns(this.columns)}
          </Table>
        </div>

        {this.bottomVnode()}
      </div>
    );
  },
};

export default {
  install(Vue) {
    Vue.component(cTable.name, cTable);
  },
};
